﻿namespace CodeSHARPer.ViewModel.Tools
{
    using CodeSHARPer.Services;
    using CodeSHARPer.ViewModel.Base;
    using MEFedMVVM.ViewModelLocator;
    using System;
    using System.ComponentModel.Composition;
    using System.Text.RegularExpressions;
    using System.Windows.Input;

    public class FindReplaceViewModel : Base.ToolViewModel
    {
        public const string ToolContentId = "FindAndReplace";

        public Cinch.SimpleCommand<object, object> DoFindCommand { get; private set; }
        public Cinch.SimpleCommand<object, object> DoReplaceCommand { get; private set; }
        public Cinch.SimpleCommand<object, object> DoReplaceAllCommand { get; private set; }

        public Func<FindReplaceViewModel, bool, bool> FindNext { get; set; }

        #region constructor
        public FindReplaceViewModel()
            : base("Find and Replace")
        {
            ContentId = ToolContentId;

            this.CurrentEditor = null;

            //commands
            DoFindCommand = new Cinch.SimpleCommand<object, object>(DoFindNext);
            DoReplaceCommand = new Cinch.SimpleCommand<object, object>(DoReplace);
            DoReplaceAllCommand = new Cinch.SimpleCommand<object, object>(DoReplaceAll);
        }
        #endregion constructor

        #region properties

        public override Uri IconSource
        {
            get { return new Uri("/Themes/Images/App/Ico/app.find.ico.ico", UriKind.RelativeOrAbsolute); }
        }

        private string mTextToFind = string.Empty;
        public string TextToFind
        {
            get { return this.mTextToFind; }
            set
            {
                if (this.mTextToFind != value)
                {
                    this.mTextToFind = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.TextToFind));
                }
            }
        }

        private string mReplacementText = string.Empty;
        public string ReplacementText
        {
            get { return this.mReplacementText; }
            set
            {
                if (this.mReplacementText != value)
                {
                    this.mReplacementText = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.ReplacementText));
                }
            }
        }

        private bool mSearchUp = false;
        public bool SearchUp
        {
            get { return this.mSearchUp; }
            set
            {
                if (this.mSearchUp != value)
                {
                    this.mSearchUp = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.SearchUp));
                }
            }
        }

        private bool mUseWildcards = false;
        public bool UseWildcards
        {
            get { return this.mUseWildcards; }
            set
            {
                if (this.mUseWildcards != value)
                {
                    this.mUseWildcards = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.UseWildcards));
                }
            }
        }

        private bool mCaseSensitive = false;
        public bool CaseSensitive
        {
            get { return this.mCaseSensitive; }
            set
            {
                if (this.mCaseSensitive != value)
                {
                    this.mCaseSensitive = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.CaseSensitive));
                }
            }
        }

        private bool mUseRegEx = false;
        public bool UseRegEx
        {
            get { return this.mUseRegEx; }
            set
            {
                if (this.mUseRegEx != value)
                {
                    this.mUseRegEx = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.UseRegEx));
                }
            }
        }

        private bool mWholeWord = false;
        public bool WholeWord
        {
            get { return this.mWholeWord; }
            set
            {
                if (this.mWholeWord != value)
                {
                    this.mWholeWord = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.WholeWord));
                }
            }
        }

        private bool mAcceptsReturn = false;
        public bool AcceptsReturn
        {
            get { return this.mAcceptsReturn; }
            set
            {
                if (this.mAcceptsReturn != value)
                {
                    this.mAcceptsReturn = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.AcceptsReturn));
                }
            }
        }

        private bool mAllowReplace = true;
        public bool AllowReplace
        {
            get { return this.mAllowReplace; }
            set
            {
                if (this.mAllowReplace != value)
                {
                    this.mAllowReplace = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.AllowReplace));
                }
            }
        }

        private bool mShowAsFind = true;
        /// <summary>
        /// Get/set property to determine whether dialog should show Find UI (true)
        /// or whether it should show Find/Replace UI elements (false).
        /// </summary>
        public bool ShowAsFind
        {
            get { return this.mShowAsFind; }
            set
            {
                if (this.mShowAsFind != value)
                {
                    this.mShowAsFind = value;

                    if (value == true)            // Focus textbox on find tab or find/replace tab
                    {
                        this.IsTextToFindFocused = true;
                        this.IsTextToFindInReplaceFocused = false;
                    }
                    else
                    {
                        this.IsTextToFindFocused = false;
                        this.IsTextToFindInReplaceFocused = true;
                    }

                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.ShowAsFind));
                }
            }
        }

        private bool mIsTextToFindFocused = true;
        public bool IsTextToFindFocused
        {
            get { return this.mIsTextToFindFocused; }
            set
            {
                if (this.mIsTextToFindFocused != value)
                {
                    this.mIsTextToFindFocused = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.IsTextToFindFocused));
                }
            }
        }

        private bool mIsTextToFindInReplaceFocused = true;
        public bool IsTextToFindInReplaceFocused
        {
            get { return this.mIsTextToFindInReplaceFocused; }
            set
            {
                if (this.mIsTextToFindInReplaceFocused != value)
                {
                    this.mIsTextToFindInReplaceFocused = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.IsTextToFindInReplaceFocused));
                }
            }
        }

        private SearchScope mSearchIn = SearchScope.CurrentDocument;
        public SearchScope SearchIn
        {
            get { return this.mSearchIn; }
            set
            {
                if (this.mSearchIn != value)
                {
                    this.mSearchIn = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.SearchIn));
                }
            }
        }

        /// <summary>
        /// Determines whether to display the Search in choice or not.
        /// 
        /// The user can use this choice to select whether results are search/replaced:
        /// 1> Only in the current document
        /// 2> In all open documents
        /// </summary>
        private bool mShowSearchIn = true;
        public bool ShowSearchIn
        {
            get { return this.mShowSearchIn; }
            private set
            {
                if (this.mShowSearchIn != value)
                {
                    this.mShowSearchIn = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<FindReplaceViewModel>(x => x.ShowSearchIn));
                }
            }
        }

        public IEditor CurrentEditor { get; set; }

        #endregion properties

        #region methods
        /// <summary>
        /// Constructs a regular expression according to the currently selected search parameters.
        /// </summary>
        /// <param name="ForceLeftToRight"></param>
        /// <returns>The regular expression.</returns>
        public Regex GetRegEx(bool ForceLeftToRight = false)
        {
            Regex r;
            RegexOptions o = RegexOptions.None;
            if (SearchUp && !ForceLeftToRight)
                o = o | RegexOptions.RightToLeft;
            if (!CaseSensitive)
                o = o | RegexOptions.IgnoreCase;

            if (UseRegEx)
                r = new Regex(TextToFind, o);
            else
            {
                string s = Regex.Escape(TextToFind);
                if (UseWildcards)
                    s = s.Replace("\\*", ".*").Replace("\\?", ".");
                if (WholeWord)
                    s = "\\W" + s + "\\W";
                r = new Regex(s, o);
            }

            return r;
        }

        public void DoFindNext(object args)
        {
            if (this.FindNext != null)
                this.FindNext(this, this.mSearchUp);
        }

        public void DoReplace(object args)
        {
            IEditor CE = GetCurrentEditor();
            if (CE == null) return;

            // if currently selected text matches -> replace; anyways, find the next match
            Regex r = GetRegEx();
            string s = CE.Text.Substring(CE.SelectionStart, CE.SelectionLength); // CE.SelectedText;
            Match m = r.Match(s);
            if (m.Success && m.Index == 0 && m.Length == s.Length)
            {
                CE.Replace(CE.SelectionStart, CE.SelectionLength, ReplacementText);
            }

            if (this.FindNext != null)
                this.FindNext(this, false);
        }

        public void DoReplaceAll(object args)
        {
            IEditor CE = GetCurrentEditor();
            if (CE == null) return;

            if (ServiceResolver.Instance.MessageBoxService.ShowYesNoCancel(string.Format(
                "Do you really want to replace all occurences of '{0}' with '{1}'?", TextToFind, ReplacementText),
                "Replace all", Cinch.CustomDialogIcons.Question) == Cinch.CustomDialogResults.Yes)
            {
                object InitialEditor = CurrentEditor;
                // loop through all Editors, until we are back at the starting Editor                
                do
                {
                    Regex r = GetRegEx(true);   // force left to right, otherwise indices are screwed up
                    int offset = 0;
                    CE.BeginChange();
                    foreach (Match m in r.Matches(CE.Text))
                    {
                        CE.Replace(offset + m.Index, m.Length, ReplacementText);
                        offset += ReplacementText.Length - m.Length;
                    }
                    CE.EndChange();

                    // XXX TODO CE = GetNextEditor();
                } while (CurrentEditor != InitialEditor);
            }
        }

        public IEditor GetCurrentEditor()
        {
            if (CurrentEditor == null)
                return null;

            if (CurrentEditor is IEditor)
                return CurrentEditor as IEditor;

            return null;
        }
        #endregion methods
    }
}
